from net_controller import *
from optparse import OptionParser

import pickle
import sys

class ByouServer:

    def __init__(self, proc_id, num_servers, options, config_path="config.json"):
        self.cmd_id = 0
        self.delay = options.delay
        self.is_primary = False
        self.max_retry = 12
        self.msg_q = []
        self.nc = NetController(proc_id, None, config_path)
        self.proc_id = proc_id
        self.num_servers = num_servers
        self.timeout = 5
        self.verbose = options.verbose

        # Bayou Variables
        self.asn = 0
        self.csn = 0
        self.rid = 0
        self.versions = [0] * num_servers

        if proc_id == 0:
            self.is_primary = True

        self.committed_db = {}
        self.db = {}

        self.committed_log = []
        self.log = []

        self.committed_log_filename = "./logs/" + str(proc_id) + ".clog"
        self.log_filename = "./logs/" + str(proc_id) + ".log"

    def anti_entropy(self, rid):

        r = self.status_of(rid)

        # SHOULD NEVER HAPPEN. But just in case.
        if r == None:
            return

        # First, do we need to send over some committed writes?
        if r["csn"] < self.csn:
            return

    def commit(self, msg):

        # We only want to log/commit changes to db.
        if msg["type"] == "add" or msg["type"] == "delete" or msg["type"] == "edit":

            print "\nCOMMIT"
            print "Committing: " + str(msg)

            # Remove msg from tentative log.
            self.log.remove(msg)

            # Remove msg from tentative db.
            if msg["key"] in self.db:
                del self.db[msg["key"]]

            # Edit msg for commit.
            msg["csn"] = self.csn
            self.csn += 1

            msg["state"] = "committed"

            # Add msg to committed log.
            self.committed_log.append(msg)

            # Apply msg to committed db.
            self.modify_db("committed", msg)

            # Write out tentative/committed logs.
            self.write_log(self.committed_log, self.committed_log_filename)
            self.write_log(self.log, self.log_filename)

            print

    def get_log_index(self, log, sn):
        
        count = 0
        index = -1

        if log == "tentative":

            for line in self.log:
                if line["asn"] == sn:
                    index = count
                    break
                count += 1

        elif log == "committed":

            for line in self.committed_log:
                if line["csn"] == sn:
                    index = count
                    break
                count += 1

        return index

    def modify_db(self, db, msg):

        action = msg["type"]
        key = msg["key"]

        if action == "add" or action == "edit":

            url = msg["url"]

            if db == "tentative":
                self.db[key] = url

            elif db == "committed":
                self.committed_db[key] = url

        elif action == "delete":

            if db == "tentative":
                if key in self.db:
                    del self.db[key]

            elif db == "committed":
                if key in self.committed_db:
                    del self.committed_db[key]

    def process_msg(self, msg):

        print "\nPROCESS_MSG"
        print "Processing: " + str(msg)

        response = None

        if msg["type"] == "add" or msg["type"] == "edit":
            msg["asn"] = self.asn
            msg["state"] = "tentative"
            self.asn += 1

            if msg["key"] in self.db:
                msg["oldurl"] = self.db[msg["key"]]
            else:
                msg["oldurl"] = ""
            self.log.append(msg)

            self.versions[self.proc_id] = msg["asn"] # Update version vector.
            self.modify_db("tentative", msg)

            response = msg

        elif msg["type"] == "delete":
            msg["asn"] = self.asn
            msg["state"] = "tentative"
            self.asn += 1

            if msg["key"] in self.db:
                msg["oldurl"] = self.db[msg["key"]]
            else:
                msg["oldurl"] = ""
            self.log.append(msg)

            self.versions[self.proc_id] = msg["asn"] # Update version vector.
            self.modify_db("tentative", msg)

            response = msg

        elif msg["type"] == "print":
            response = self.committed_db
            response.update(self.db) # Add in other db.

        elif msg["type"] == "commit":
            response = msg

            msg_to_commit = msg["msg_to_commit"]
            self.commit(msg_to_commit)

        elif msg["type"] == "statusreq":
            status = {"csn" : self.csn, "roc_id" : self.proc_id,"versions" : self.versions}
            response = msg
            response["status"] = status
            response["type"] = "status"


        print "Responding: " + str(response) + " to " + str(msg["proc_id"])
        print

        self.send(msg["proc_id"], response)

        self.write_log(self.log, self.log_filename)

        if self.is_primary:
            self.commit(msg)

    def run(self, limit=0):

        count = 0
        print "\nRunning server..."

        while True:

            if limit > 0 and count == limit:
                print "\nLimit reached!"
                return

            nc_return = self.nc.next()

            if nc_return != None:
                if limit > 0: count += 1
                self.msg_q.append(nc_return[1])

            if self.msg_q:
                msg = self.msg_q.pop(0)
                print "Adding " + str(msg) + " to msg_q.\n"
                self.process_msg(msg)

            else:
                self.rid = (self.rid + 1) % self.num_servers

                if self.rid == self.proc_id:
                    self.rid = (self.rid + 1) % self.num_servers

                if self.rid == self.proc_id:
                    continue

                if self.verbose: print "anti_entropy(" + str(self.proc_id) + ", " + str(self.rid) + ")"

                self.anti_entropy(self.rid)
    
    def send(self, proc_id, msg):
        # if self.verbose: print "send(" + str(proc_id) + ", " + str(msg) + ")\n"
        self.nc.send(proc_id, msg, self.delay)

    def send_all(self, msg):
        self.nc.send_all(msg, False, self.delay)

    def status_of(self, rid):

        print "\nSTATUS_OF (" + str(rid) + ")"

        msg = {"cmd_id" : self.cmd_id, "proc_id" : self.proc_id, "type" : "statusreq"}
        old_cmd_id = self.cmd_id
        self.cmd_id += 1

        print "Sending -> " + str(rid) + ": " + str(msg)
        self.send(rid, msg)

        while True:

            nc_return = self.nc.next(True, self.timeout)

            if nc_return != None and nc_return[1] != None:
                response = nc_return[1]

                print "Response: " + str(response)

                if response["cmd_id"] == old_cmd_id and response["proc_id"] == self.proc_id and response["type"] == "status":
                    status = response["status"]
                    if status["roc_id"] == rid:
                        print "Receive: " + str(status)
                        return status

                else:
                    if self.verbose: print "Append to msg_q."
                    self.msg_q.append(nc_return[1])
                    self.process_msg(self.msg_q.pop(0))

            else:
                if self.verbose: print "Sending: " + str(msg)
                self.send(rid, msg)

    def write_log(self, log, log_filename):

        # Just write the regular log file.
        log_file = open(log_filename, "w")
        for line in log:
            log_file.write(str(line))
            log_file.write('\n')
        log_file.close()

        # For when we need to serialize:
        #log_file = open(log_filename, "wb")
        #pickle.dump(log, log_file)
        #log_file.close()

    def test(self):

        print "Running test..."
        self.run(5)
        index = self.get_log_index("committed", 2)

        print "Index of csn = 2: " + str(index)


if __name__ == "__main__":

    usage = "usage: %prog [options] proc_id num_servers"
    parser = OptionParser(usage=usage)

    parser.add_option("-d", "--delay", default=0, dest="delay",
                      help="set message delay", metavar="<n>", type="int")
    parser.add_option("-v", "--verbose", default=False, dest="verbose", action="store_true",
                      help="verbose output")

    (options, args) = parser.parse_args()

    if len(args) < 2:
        parser.print_help()
        sys.exit(1)

    print "delay =       ", options.delay
    print "proc_id =     ", args[0]
    print "num_servers = ", args[1]

    process = ByouServer(int(args[0]), int(args[1]), options)
    # time.sleep(5)
    process.run()
